package com.jlf.core.engine.driver.impl;

import com.baomidou.mybatisplus.extension.toolkit.SqlRunner;
import com.jlf.core.constant.Constants;
import com.jlf.core.engine.builder.*;
import com.jlf.core.engine.domain.SchemaCriterionDO;
import com.jlf.core.engine.domain.SchemaDomainDO;
import com.jlf.core.engine.domain.SchemaFieldDO;
import com.jlf.core.engine.domain.SchemaIndexDO;
import com.jlf.core.engine.entity.MetaTable;
import com.jlf.core.engine.meta.MetaRepository;
import com.jlf.core.engine.persistence.SchemaCriterionPersistence;
import com.jlf.core.engine.persistence.SchemaDomainPersistence;
import com.jlf.core.engine.persistence.SchemaFieldPersistence;
import com.jlf.core.engine.persistence.SchemaIndexPersistence;
import com.jlf.core.engine.driver.SchemaDriveServer;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.binding.MapperProxyFactory;
import org.apache.ibatis.binding.MapperRegistry;
import org.apache.ibatis.session.SqlSessionFactory;
import org.joor.Reflect;
import org.mybatis.spring.mapper.MapperFactoryBean;
import org.springframework.aop.framework.ProxyProcessorSupport;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.*;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.lang.NonNull;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 模型的驱动服务
 *
 * @author wujr
 * 2023/7/8
 * @version 1.0
 * 【变更历史】
 * [wujr 2023/7/8 1.0 新增]
 */
@Slf4j
@RequiredArgsConstructor
public class SchemaDriveServerImpl implements SchemaDriveServer, ApplicationContextAware, ApplicationListener<ApplicationReadyEvent> {
    private final SqlSessionFactory sqlSessionFactory;
    private final DataSourceTransactionManager dataSourceTransactionManager;
    private final TransactionDefinition transactionDefinition;
    private final RequestMappingHandlerMapping requestMappingHandlerMapping;
    private final MetaRepository metaRepository;
    private final SchemaDomainPersistence schemaDomainPersistence;
    private final SchemaFieldPersistence schemaFieldPersistence;
    private final SchemaIndexPersistence schemaIndexPersistence;
    private final SchemaCriterionPersistence schemaCriterionPersistence;
    private DefaultListableBeanFactory defaultListableBeanFactory;
    private ApplicationContext applicationContext;
    private AutoClassLoader engineClassLoader;
    private Object openapiWebMvcProvider;

    @Override
    public void publishDomain(SchemaDomainDO schemaDomain, List<SchemaFieldDO> schemaFields,
                              List<SchemaIndexDO> schemaIndexes,
                              List<SchemaCriterionDO> schemaCriteria,
                              boolean initTable) throws Exception {
        AutoDomainBuilder domainBuilder = new AutoDomainBuilder(engineClassLoader, schemaDomain, schemaFields);
        domainBuilder.generate();
        String domainSimpleCode = StringUtils.capitalize(schemaDomain.getDomainCode());
        AutoDaoBuilder daoBuilder = new AutoDaoBuilder(domainBuilder);
        Class<?> daoClass = daoBuilder.generate();
        AutoPersistenceBuilder persistenceBuilder = new AutoPersistenceBuilder(daoBuilder);
        Class<?> persistenceClass = persistenceBuilder.generate();
        Class<?> persistenceImplClass = persistenceBuilder.generateImpl();
        AutoServerBuilder serverBuilder = new AutoServerBuilder(persistenceBuilder, schemaDomain);
        Class<?> serverClass = serverBuilder.generate();
        Class<?> serverImplClass = serverBuilder.generateImpl();
        AutoControllerBuilder controllerBuilder = new AutoControllerBuilder(serverBuilder, schemaDomain, schemaFields, schemaCriteria);
        controllerBuilder.generate();

        BeanDefinitionBuilder beanDefinitionBuilder;
        // 需要的类已经动态创建完毕
        // 需要：把dao注入到mybatis-plus中
        // 把persistence、server、controller注入到bean中
        // 把controller开放为接口
        // 注册：dao的bean
        String daoBeanName = schemaDomain.getDomainCode() + "Dao";
        Object daoBean = loadBean(daoBeanName, daoClass);
        if (daoBean == null) {
            MapperFactoryBean<?> factoryBean = new MapperFactoryBean<>(daoClass);
            factoryBean.setSqlSessionFactory(sqlSessionFactory);
            sqlSessionFactory.getConfiguration().addMapper(daoClass);
            // 注册 mapper
            try {
                registerBean(daoBeanName, factoryBean.getObject());
            } catch (Exception e) {
                log.warn("register BaseMapper error：", e);
            }
        }

        // 注册：persistence的bean
        String persistenceBeanName = schemaDomain.getDomainCode() + "Persistence";
        Object persistentBean = loadBean(persistenceBeanName, persistenceClass);
        if (persistentBean == null) {
            beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(persistenceClass);
            GenericBeanDefinition definition = (GenericBeanDefinition) beanDefinitionBuilder.getBeanDefinition();
            definition.setInstanceSupplier(() -> generatePersistence(persistenceImplClass));
            definition.setAutowireMode(GenericBeanDefinition.AUTOWIRE_BY_TYPE);
            defaultListableBeanFactory.registerBeanDefinition(persistenceBeanName, definition);
        }
        // 注册：server的bean
        String serverBeanName = schemaDomain.getDomainCode() + "Server";
        Object serverBean = loadBean(serverBeanName, serverClass);
        if (serverBean == null) {
            beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(serverClass);
            GenericBeanDefinition definition = (GenericBeanDefinition) beanDefinitionBuilder.getBeanDefinition();
            definition.setInstanceSupplier(() -> generateServer(serverImplClass, persistenceBeanName, persistenceClass));
            definition.setAutowireMode(GenericBeanDefinition.AUTOWIRE_BY_TYPE);
            defaultListableBeanFactory.registerBeanDefinition(serverBeanName, definition);
        }
        // 注册：controller的bean
        String controllerBeanName = schemaDomain.getDomainCode() + "Controller";
        Object controllerBean = loadBean(controllerBeanName, controllerBuilder.getControllerClass());
        if (controllerBean == null) {
            beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(controllerBuilder.getControllerClass());
            defaultListableBeanFactory.registerBeanDefinition(controllerBeanName, beanDefinitionBuilder.getBeanDefinition());
            // 注册接口
            registerController(controllerBeanName);
        }
        log.info("构建模型领域：{} 的代码完毕！", schemaDomain.getDomainCode());
        if (initTable){
            // 需要初始化数据库
            initDomainTable(schemaDomain, schemaFields, schemaIndexes);
        }
    }

    @Override
    public void withdrawDomain(SchemaDomainDO schemaDomain) throws Exception {
        String domainClassCode = StringUtils.capitalize(schemaDomain.getDomainCode());
        String daoClassName = schemaDomain.daoPackage() + "." + domainClassCode + "Dao";
        String controllerClassName = schemaDomain.controllerPackage() + "." + domainClassCode + "Controller";
        String daoBeanName = schemaDomain.getDomainCode() + "Dao";
        String persistenceBeanName = schemaDomain.getDomainCode() + "Persistence";
        String serverBeanName = schemaDomain.getDomainCode() + "Server";
        String controllerBeanName = schemaDomain.getDomainCode() + "Controller";
        Class<?> daoClass = engineClassLoader.loadClass(daoClassName);
        // 撤销controller
        Class<?> controllerClass = engineClassLoader.loadClass(controllerClassName);
        unregisterController(controllerClass);
        // 注销controller的bean
        defaultListableBeanFactory.removeBeanDefinition(controllerBeanName);
        // 注销server的bean
        defaultListableBeanFactory.removeBeanDefinition(serverBeanName);
        // 注销Persistence的bean
        defaultListableBeanFactory.removeBeanDefinition(persistenceBeanName);
        // 注销dao的bean
        Object daoBean = loadBean(daoBeanName, daoClass);
        unregisterBean(daoBean);
        // mybatis注销该mapper
        removeMapper(daoClass);
    }
    private void removeMapper(Class<?> mapper){
        MapperRegistry mapperRegistry = sqlSessionFactory.getConfiguration().getMapperRegistry();
        Map<Class<?>, MapperProxyFactory<?>> knownMappers = Reflect.on(mapperRegistry).get("knownMappers");
        knownMappers.remove(mapper);
    }

    private Object generatePersistence(Class<?> implClass){
        try {
            return implClass.newInstance();
        }catch (Exception e){
            log.warn("动态生成persistence的bean实例异常：", e);
            return null;
        }
    }
    private Object generateServer(Class<?> implClass, String persistenceBeanName, Class<?> persistenceBeanClass){
        try {
            Object persistenceBean = loadBean(persistenceBeanName, persistenceBeanClass);
            return implClass.getDeclaredConstructor(persistenceBeanClass).newInstance(persistenceBean);
        }catch (Exception e){
            log.warn("动态生成server的bean实例异常：", e);
            return null;
        }
    }

    @Override
    public void setApplicationContext(@NonNull ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
        this.defaultListableBeanFactory = (DefaultListableBeanFactory)applicationContext.getAutowireCapableBeanFactory();
        ClassLoader loader = this.defaultListableBeanFactory.getBeanClassLoader();
        engineClassLoader = new AutoClassLoader();
        engineClassLoader.setDefaultClassLoader(loader);
        this.defaultListableBeanFactory.setBeanClassLoader(engineClassLoader);
        Map<String, ? extends ProxyProcessorSupport> mapOfProxy = this.applicationContext.getBeansOfType(ProxyProcessorSupport.class);
        for (Map.Entry<String, ? extends ProxyProcessorSupport> entry : mapOfProxy.entrySet()){
            ProxyProcessorSupport proxy = entry.getValue();;
            proxy.setBeanClassLoader(engineClassLoader);
        }
        // openapi3的支持
        this.openapiWebMvcProvider = this.defaultListableBeanFactory.getBean("springWebProvider");
    }
    public void registerController(String controllerBeanName) throws Exception {
        //注册Controller
        if (this.openapiWebMvcProvider != null){
            // 支持动态注入到openapi中
            Reflect.on(this.openapiWebMvcProvider).set("handlerMethods", null);
        }
        Method method = requestMappingHandlerMapping.getClass().getSuperclass().getSuperclass().
                getDeclaredMethod("detectHandlerMethods", Object.class);
        //将private改为可使用
        method.setAccessible(true);
        method.invoke(requestMappingHandlerMapping, controllerBeanName);
    }
    private void unregisterController(Class<?> controllerClass){
        ReflectionUtils.doWithMethods(controllerClass, method -> {
            Method specificMethod = ClassUtils.getMostSpecificMethod(method, controllerClass);
            try {
                Method createMappingMethod = RequestMappingHandlerMapping.class.
                        getDeclaredMethod("getMappingForMethod", Method.class, Class.class);
                createMappingMethod.setAccessible(true);
                RequestMappingInfo requestMappingInfo = (RequestMappingInfo)
                        createMappingMethod.invoke(requestMappingHandlerMapping, specificMethod, controllerClass);
                if (requestMappingInfo != null) {
                    requestMappingHandlerMapping.unregisterMapping(requestMappingInfo);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, ReflectionUtils.USER_DECLARED_METHODS);
    }

    public <T> void registerBean(String beanName, T bean) {
        ConfigurableApplicationContext context = (ConfigurableApplicationContext)applicationContext;
        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        try {
            beanFactory.getBean(beanName);
        }catch (Exception e) {
            beanFactory.registerSingleton(beanName, bean);
        }
    }
    public <T> void unregisterBean(T bean){
        ConfigurableApplicationContext context = (ConfigurableApplicationContext)applicationContext;
        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        beanFactory.destroyBean(bean);
    }
    public <T> T loadBean(String beanName, Class<T> clz){
        ConfigurableApplicationContext context = (ConfigurableApplicationContext)applicationContext;
        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        try{
            return beanFactory.getBean(beanName, clz);
        }catch (Exception e){
            log.trace("加载bean：{} - {} 异常：", clz, beanName, e);
            return null;
        }
    }

    @Override
    public void onApplicationEvent(@NonNull ApplicationReadyEvent event) {
        try {
            // 加载已有的元数据
            metaRepository.load(null);
            log.trace("加载数据库：{}，版本：{}，元数据完毕！", metaRepository.getDbProduct(), metaRepository.getDbVersion());
            // 初始化模型领域数据表
            List<SchemaDomainDO> schemaDomains = new ArrayList<>();
            Map<String, List<SchemaFieldDO>> mapDomainOfFields = new HashMap<>();
            Map<String, List<SchemaIndexDO>> mapDomainOfIndexes = new HashMap<>();
            Map<String, List<SchemaCriterionDO>> mapDomainOfCriteria = new HashMap<>();
            initDomainTable(schemaDomains, mapDomainOfFields, mapDomainOfIndexes, mapDomainOfCriteria);
            for (SchemaDomainDO schemaDomain : schemaDomains){
                List<SchemaFieldDO> schemaFields = mapDomainOfFields.get(schemaDomain.getDomainCode());
                List<SchemaIndexDO> schemaIndexes = mapDomainOfIndexes.get(schemaDomain.getDomainCode());
                List<SchemaCriterionDO> schemaCriteria = mapDomainOfCriteria.get(schemaDomain.getDomainCode());
                // 发布该模型
                publishDomain(schemaDomain, schemaFields, schemaIndexes, schemaCriteria,false);
            }
        }catch (Exception e){
            log.warn("加载数据库的元数据异常：", e);
        }
    }
    private void initDomainTable(List<SchemaDomainDO> outputDomains,
                                 Map<String, List<SchemaFieldDO>> outputOfFields,
                                 Map<String, List<SchemaIndexDO>> outputOfIndexes,
                                 Map<String, List<SchemaCriterionDO>> outputOfCriteria){
        List<SchemaDomainDO> schemaDomains = schemaDomainPersistence.loadValid();
        if (CollectionUtils.isEmpty(schemaDomains)){
            log.info("初始化模型领域数据表，当前无有效的模型领域");
            return;
        }
        outputDomains.addAll(schemaDomains);
        List<String> domainCodes = schemaDomains.stream().map(SchemaDomainDO::getDomainCode).collect(Collectors.toList());
        // 加载字段
        List<SchemaFieldDO> schemaFields = schemaFieldPersistence.loadByDomains(domainCodes, Constants.Status.enabled);
        Map<String, List<SchemaFieldDO>> mapDomainOfFields = schemaFields.stream().collect(Collectors.groupingBy(SchemaFieldDO::getDomainCode));
        outputOfFields.putAll(mapDomainOfFields);
        // 加载索引
        List<SchemaIndexDO> schemaIndexes = schemaIndexPersistence.loadByDomains(domainCodes, Constants.Status.enabled);
        Map<String, List<SchemaIndexDO>> mapDomainOfIndexes = schemaIndexes.stream().collect(Collectors.groupingBy(SchemaIndexDO::getDomainCode));
        outputOfIndexes.putAll(mapDomainOfIndexes);
        // 加载检索条件
        List<SchemaCriterionDO> schemaCriteria = schemaCriterionPersistence.loadByDomains(domainCodes, Constants.Status.enabled);
        Map<String, List<SchemaCriterionDO>> mapDomainOfCriteria = schemaCriteria.stream().collect(Collectors.groupingBy(SchemaCriterionDO::getDomainCode));
        outputOfCriteria.putAll(mapDomainOfCriteria);

        for (SchemaDomainDO schemaDomain : schemaDomains){
            if (!mapDomainOfFields.containsKey(schemaDomain.getDomainCode())){
                log.info("模型领域：{} 没有配置字段，无需进行初始化", schemaDomain.getDomainCode());
                continue;
            }
            schemaFields = mapDomainOfFields.get(schemaDomain.getDomainCode());
            schemaIndexes = mapDomainOfIndexes.getOrDefault(schemaDomain.getDomainCode(), null);
            initDomainTable(schemaDomain, schemaFields, schemaIndexes);
        }
    }
    private void initDomainTable(SchemaDomainDO schemaDomain,
                                 List<SchemaFieldDO> schemaFields,
                                 List<SchemaIndexDO> schemaIndexes){
        List<String> sqles = new ArrayList<>();
        MetaTable metaTable = metaRepository.loadTable(schemaDomain.getDomainTable());
        if (metaTable == null){
            // 还没有该领域的数据表
            sqles = new AutoSqlTableBuilder(metaRepository, schemaDomain, schemaFields, schemaIndexes).generate();
            // 执行这些语句
        }else {
            List<SchemaFieldDO> newFields = new ArrayList<>();
            List<SchemaFieldDO> modFields = new ArrayList<>();
            List<SchemaFieldDO> delFields = new ArrayList<>();
            List<SchemaIndexDO> newIndexes = new ArrayList<>();
            List<SchemaIndexDO> modIndexes = new ArrayList<>();
            List<SchemaIndexDO> delIndexes = new ArrayList<>();
            if (metaRepository.difference(metaTable, schemaDomain, schemaFields, schemaIndexes, newFields, modFields, delFields, newIndexes, modIndexes, delIndexes)){
                // 该领域有版本变更，仅进行新增字段、变更字段，不进行字段的删除
            }
        }
        if (!CollectionUtils.isEmpty(sqles)){
            // 手动开启事务
            TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);
            try(SqlRunner sqlRunner = SqlRunner.db()){
                for (String sql : sqles){
                    sqlRunner.update(sql);
                }
                // 手动提交事务
                dataSourceTransactionManager.commit(transactionStatus);
            }catch (Exception e){
                // 手动回滚事务
                dataSourceTransactionManager.rollback(transactionStatus);
                log.warn("初始化模型：{} 的数据表异常：", schemaDomain.getDomainCode(), e);
            }
        }
    }
}
